﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using WPFGestures.Gestures;
using Gestures;
using Common;
using WPFGestures.Gestures.WPF;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Windows.Media.Effects;
using System.IO;
//using Microsoft.WPF.Framework.Graphics;
//using GameSpace;
//using WPFGestures.Gestures.WPF;
//using Microsoft.WPF.Framework;

namespace GesturePreviews.Gestures
{
    public class GestureManager
    {
        Transformer tf;
        public CandidateLibrary candidateLibrary;
        Dictionary<int, Gesture> gestures;

        // contains all gesture segments (i.e. parts of gestures).
        Dictionary<string, Segment> segmentsDatabase;

        Dictionary<int, List<Preview>> previews = new Dictionary<int, List<Preview>>();

        // time that finger should be still until gesture should be started.
        Dictionary<int, DateTime> gestureTimeouts = new Dictionary<int, DateTime>();

        Dictionary<int, bool> validGesture =new Dictionary<int, bool>();

        Dictionary<int, string> clickGestures = new Dictionary<int, string>();
        Dictionary<int, Transform> clickGesturePositions = new Dictionary<int, Transform>();

        public GestureManager()
        {
            segmentsDatabase = new Dictionary<string, Segment>();
            
            tf = new Transformer();
            candidateLibrary = new CandidateLibrary();
            gestures = new Dictionary<int, Gesture>();

            
        }

        public bool init()
        {
            previewsEnabled = true;
            return true;
        }


        bool previewsEnabled;

        public void setPreviewsEnabled(bool value)
        {
            previewsEnabled = value;
        }

        // return true als punt processed is.
        // return false als punt nog verwerkt kan / moet worden door applicatie
        public bool touchAdded(int id, float x, float y, TimeSpan gestureTimeout)
        {           
            if (gestures.ContainsKey(id))
            {
                throw new Exception("finger ID already exists");
                // gesture bestaat al, of er is al een timeout voor gesture. Negeer.
                //return true;
            }

            WPFGesture WPF = new WPFGesture(id);
            WPF.addPoint(new Common.Point(x, y));
            gestures.Add(id, WPF);

            // kijk of we al previews kunnen laten zien of dat er een timeout is.
            if (gestureTimeout.TotalMilliseconds > 0)
            {
                // eerst een timeout
                gestureTimeouts.Add(id, DateTime.Now.Add(gestureTimeout));
                // in de tussentijd: process punt nog in applicatie
                return true;
            }

            validGesture.Add(id, true);
            drawPreviews(id, x, y);

            return true;

            //if (isGesture.Contains(id))

            //if (gestureTimeout.Milliseconds > 0)
            //{
                //gestureTimeouts.Add(id, DateTime.Now.Add(gestureTimeout));
                //return false;
            //}
        }

        public void drawPreviews(int id, float x, float y)
        {


            List<Preview> pv = new List<Preview>();

            if (previewsEnabled)
            {
                List<String> lst = SegmentTree.getFollowUps(null);
                foreach (Candidate c in candidateLibrary.candidates.Values)
                {
                    if (lst.Contains(c.name))
                    {
                        WPFPreview xp = new WPFPreview();
                        xp.load(c, new Common.Point(x, y), "", c.source, 1, 1);
                        pv.Add(xp);
                    }
                }
            }

            previews.Add(id, pv);

        }


        // return false als data door applicatie nog verwerkt kan worden
        public bool touchMoved(int id, float x, float y)
        {

            gestures[id].addPoint(new Common.Point(x, y));

            if (gestureTimeouts.ContainsKey(id))
            {
                // we wachten nog op timeout, laat app punt verwerken
                //gestures[id].addPoint(new Common.Point(x, y));
                return false;
            }

            if (!validGesture.ContainsKey(id) || validGesture[id] == false)
            {
                // geen valide gesture, laat app punt verwerken
                return false;
            }

            //            if (!gestures.ContainsKey(id))
            //{
                //return false;
                //throw new Exception("touch id not found in database");
            //}

            //validGesture.Add(id, true);

            


            return true;

            

            //return true;
            //if (
        }


        public Match touchDeleted(int id)
        {

            if (previews.ContainsKey(id))
            {
                foreach (Preview p in previews[id])
                {
                    p.delete();
                }
            }

            if (clickGestures.ContainsKey(id))
            {

                Match m = new Match(clickGestures[id], 0);

                makeFeedback(gestures[id].getPoints()[0].x, gestures[id].getPoints()[0].y, candidateLibrary.candidates[m.name].source);


                Console.WriteLine("found click match: " + m.name );

                //TimeSpan ts = DateTime.Now.Subtract(gestures[id].times[0]);
                storeGestureData(gestures[id], m, true);


                clickGestures.Remove(id);
                gestures.Remove(id);
                gestureTimeouts.Remove(id);

                return m;
            }


            if (gestureTimeouts.ContainsKey(id)) {
                gestures.Remove(id);
                gestureTimeouts.Remove(id);
                return null;
            }
            
            if (!validGesture.ContainsKey(id) || validGesture[id] == false) {
                gestures.Remove(id);
                return null;
            }

            // kijk of gesture al wel gestart is of dat er een timeout aan hangt.


            //if (!gestures.ContainsKey(id))
            //{
                // throw new Exception("touch id has not been used before.");
                //Console.WriteLine("touch id has not been used before.");
                //return null;
            //}


            List<Common.Point> points = gestures[id].getPoints();

            // TODO: fix this ugly hack. Do we want finish() in the Gesture class or not?
            //((WPFPreview)gestures[id]).onFinish();

            gestures[id].setFingerReleased();

            //gestures[id].

   
            //previews.Remove(id);

            if (points.Count < 2)
            {
                return null;
            }

            // flip gesture upside down because of stupid XML
            foreach (Common.Point p in points)
            {
                p.y = 0 - p.y;
            }

            // TODO: perhaps do resampling in Candidate Library instead to be able to match multiple resolutions?
            tf.setResamplingRate(64);
            Match match = candidateLibrary.returnBestMatch(tf.transform(points));

            // convert gesture coordinates to screen

            //List<Common.Point> points = gestures[id].getPoints();

            float x = points[points.Count - 1].x;
            float y = 0 - points[points.Count - 1].y;

            if (match.score < 20)
            {
                makeFeedback(x, y, candidateLibrary.candidates[match.name].source);
            }

            

            storeGestureData(gestures[id], match, false);

            Console.WriteLine("found match: " + match.name + " - " + match.score);

            gestures.Remove(id);
            gestureTimeouts.Remove(id);

            // kopieer plaatje

            return match;
     


        }


        public void storeGestureData(Gesture g, Match m, bool isClickGesture)
        {
            try
            {
                StreamWriter SW;
                SW = File.AppendText("C:\\gestures.txt");

                TimeSpan ts = DateTime.Now.Subtract(g.times[0]);
                if (isClickGesture)
                {
                    SW.WriteLine("clickgesture");
                    SW.WriteLine("time " + g.times[0]);
                    SW.WriteLine("match " + m.name);
                    SW.WriteLine("msduration " + ts.TotalMilliseconds);
                    SW.WriteLine();
                    //Console.WriteLine("Text Appended Successfully");


                }
                else
                {
                    SW.WriteLine("normalgesture");
                    SW.WriteLine("time " + g.times[0]);
                    SW.WriteLine("match " + m.name);
                    SW.WriteLine("score " + m.score);
                    SW.WriteLine("msduration " + ts.TotalMilliseconds);

                    //List<Common.Point> pts = g.getPoints();
                    foreach (Common.Point p in g.getPoints())
                    {
                        SW.WriteLine(p.x + " " + p.y); 
                    }
                    SW.WriteLine();
                }
                
                SW.Close();
            }
            catch (Exception e)
            {
            }
        }

        List<Image> feedbackImages = new List<Image>();

        private void makeFeedback(double x, double y, string imageString)
        {
            if (imageString != "groningen.jpg" && imageString != "boston.jpg" && imageString != "office.gif"
                && imageString != "parking.jpg" && imageString != "maps.jpg" && imageString != "earth.jpg")
            {
                return;
            }
            Canvas canvas = WPFGesture.getCanvas();

            

            Image image = new Image();
            image.Width = 200;

            feedbackImages.Add(image);

            BitmapImage myBitmapImage = new BitmapImage();
            myBitmapImage.BeginInit();
            string imageSource = Environment.CurrentDirectory + @"\images\" + imageString;
            myBitmapImage.UriSource = new Uri(imageSource);
            myBitmapImage.DecodePixelWidth = 200;
            myBitmapImage.EndInit();
            
            image.Source = myBitmapImage;
            image.Opacity = 1.0;
            // calculate end point of preview path
            //float endx = (float)(points[points.Count - 1].x) * 1000 + position.x + candidate.relativeWidth;
            //float endy = position.y - (float)(points[points.Count - 1].y) * 1000 + candidate.relativeHeight;
            TransformGroup tg = new TransformGroup();
            tg.Children.Add(new TranslateTransform(x - 100,  y - 100));
            image.RenderTransform = tg;

            image.Effect = new DropShadowEffect() { Color = Colors.White, ShadowDepth = 0, Opacity = 0.75 };
            
            canvas.Children.Add(image);
            DoubleAnimation da = new DoubleAnimation(0.0, new Duration(new TimeSpan(0, 0, 0, 0, 500)));

            da.Completed+=new EventHandler(da_Completed);
            image.BeginAnimation(Image.OpacityProperty,da );
        }

        void da_Completed(object sender, EventArgs e)
        {
            foreach (Image i in feedbackImages)
            {
                WPFGesture.getCanvas().Children.Remove(i);
            }          
        }

        public void update()
        {
            // first check whether any timeout has passed. If so we can start displaying that gesture.

            List<int> toDelete = new List<int>();
            foreach (int id in gestureTimeouts.Keys)
            {
                TimeSpan ts = gestureTimeouts[id].Subtract(DateTime.Now);
                
                //DateTim
                //int timediff = gestureTimeouts[id].CompareTo(DateTime.Now);
                if (ts.TotalMilliseconds <= 0)
                {
                    toDelete.Add(id);
                    List<Common.Point> lst = gestures[id].getPoints();

                    if(lst.Count == 1) {
                        validGesture.Add(id, true);
                    // finger has not moved for the entire duration:
                    //if (gestures[id].getPoints().Count == 1)
                    //{
                        // if so, add previews
                        drawPreviews(id, lst[0].x, lst[0].y);
                        
                    }
                }
            }

            foreach (int id in toDelete)
            {
                gestureTimeouts.Remove(id);
                if (!validGesture.ContainsKey(id) || validGesture[id] == false) {                
                    gestures.Remove(id);
                }
            }




            List<int> removals = new List<int>();

            foreach (int id in gestures.Keys)
            {
                // doe niks als we nog op timeout wachten
                if (gestureTimeouts.ContainsKey(id))
                {
                    break;
                }

                if (gestures[id].canBeDeleted())
                {
                    removals.Add(id);
                }
                else if (gestures[id].finished)
                {
                    // do nothing
                } else {
                
                    gestures[id].update();
                    gestures[id].draw();

                    if (!gestures[id].finished)
                    {
                        foreach (Preview p in previews[id])
                        {
                            // if image is clicked
                            WPFPreview wpf = (WPFPreview)p;
                            List<Preview> pv = new List<Preview>();

                            if (wpf.imageClicked())
                            {

                                // place new set of previews based on what icon was clicked
                                gestures[id].clickMode = true;
                                gestures[id].detectedSoFar.Add(wpf.segmentName);

                                foreach (Preview pr in previews[id])
                                {
                                    pr.delete();
                                }
                                previews.Remove(id);

                                List<String> followUps = SegmentTree.getFollowUps(gestures[id].detectedSoFar);

                                if (followUps.Count == 0)
                                {

                                    gestures[id].finished = true;
                                    previews.Add(id, new List<Preview>());
                                    // gesture is finished!

                                    String final = "";

                                    foreach(String s in gestures[id].detectedSoFar) {
                                        final += s;
                                        //Console.Write(s);
                                    }

                                    switch (final)
                                    {
                                        case "gleftcornergdownoverlay":
                                            clickGestures.Add(id, "gleftcornerdown");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;
                                        case "gleftcornergupoverlay":
                                            clickGestures.Add(id, "gleftcornerup");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;
                                        case "gcirclegleft":
                                            clickGestures.Add(id, "gcircleleft");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;
                                        case "gcirclegright":
                                            clickGestures.Add(id, "gcircleright");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;
                                        case "grightcornergdownphone":
                                            clickGestures.Add(id, "grightcornerdown");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;
                                        case "grightcornergupphone":
                                            clickGestures.Add(id, "grightcornerup");
                                            clickGesturePositions.Add(id, wpf.image.RenderTransform);
                                            break;

                                    }

                                    

                                    
                                }
                                else
                                {
                                    foreach (Candidate c in candidateLibrary.candidates.Values)
                                    {
                                        if (followUps.Contains(c.name))
                                        {
                                            WPFPreview xp = new WPFPreview();
                                            System.Windows.Point point = wpf.image.RenderTransform.Transform(new System.Windows.Point(0,0));
                                            xp.load(c, new Common.Point((float)point.X, (float)point.Y), "", c.source, 1, 1);
                                            pv.Add(xp);// previews.Add(id, xp);
                                        }
                                    }

                                    previews.Add(id, pv);
                                }
                                // updatePreviews(id, wpf.segmentName);
                            }
                        }
                    }


                    if (gestures[id].justPaused()) {
                        // matching
                        List<Common.Point> points = gestures[id].getPoints();

                        if (points.Count > 1)
                        {
                            Common.Point lastPoint = new Common.Point(points[points.Count - 1].x, points[points.Count - 1].y);
                            foreach (Common.Point p in points)
                            {
                                p.y = 0 - p.y;
                            }


                            Transformer tf = new Transformer();
                            tf.setResamplingRate(64);
                            List<Common.Point> lst = tf.transform(points);

                            // match
                            Match match = candidateLibrary.returnBestMatch(lst);
                            if (match != null || match.name == null)
                            {
                                gestures[id].detectedSoFar.Add(match.name);
                            }
                            //Console.WriteLine("match = " + match.name);

                            foreach (Common.Point p in points)
                            {
                                p.y = 0 - p.y;
                            }

                            //points.Clear();
                            //points.Add(lastPoint);

                            foreach (Preview p in previews[id])
                            {
                                p.delete();
                            }
                            previews.Remove(id);
                            // add new set of previews based on what follows the match.
                            List<Preview> pv = new List<Preview>();

                            //List<String> currentSegments = new List<string>();
                            //currentSegments.Add(match.name);
                            List<string> followUps = SegmentTree.getFollowUps(gestures[id].detectedSoFar);

                            if (followUps.Count == 0)
                            {
                                gestures[id].finished = true;
                                // gesture is finished!
                            }
                            else
                            {
                                foreach (Candidate c in candidateLibrary.candidates.Values)
                                {
                                    if (followUps.Contains(c.name))
                                    {
                                        WPFPreview xp = new WPFPreview();
                                        xp.load(c, lastPoint, "", c.source, 1, 1);
                                        pv.Add(xp);// previews.Add(id, xp);
                                    }
                                }
                                
                                previews.Add(id, pv);
                            }

                            

                        }


                    }
                    else if (gestures[id].isPaused())
                    {
                        //if (gestures[id].isPaused())
                        //{

                        WPFGesture wpf = (WPFGesture)gestures[id];
                        wpf.path.Stroke = Brushes.Red;

                        // Try to recognize segment.
                        //List<Common.Point> points = gestures[id].getPoints();
                        //foreach (Common.Point p in points)
                        //{
                        //p.y = 0 - p.y;
                        //}



                        foreach (Preview p in previews[id])
                        {

                            WPFPreview wp = (WPFPreview)p;
                            wp.path.Opacity = Math.Min(1, wp.image.Opacity + 0.1);
                            wp.image.Opacity = Math.Min(1, wp.image.Opacity + 0.1);
                            //DoubleAnimation da = new DoubleAnimation(wp.image.Opacity, 1, new Duration(TimeSpan.FromSeconds(0.5)));
                            //wp.image.BeginAnimation(Image.OpacityProperty, da, HandoffBehavior.SnapshotAndReplace);

                            //DoubleAnimation da2 = new DoubleAnimation(wp.path.Opacity, 1, new Duration(TimeSpan.FromSeconds(0.5)));
                            //wp.path.BeginAnimation(Path.OpacityProperty, da2, HandoffBehavior.SnapshotAndReplace);

                        }

                        //if (gestures[id].justPaused()) 

                        //wpf.image.RenderTransform = new ScaleTransform(1.0, 1.0);
                    }
                    else
                    {
                        WPFGesture wpf = (WPFGesture)gestures[id];
                        //wpf.
                        wpf.path.Stroke = Brushes.Green;
                        foreach (Preview p in previews[id])
                        {
                            WPFPreview wp = (WPFPreview)p;

                            //DoubleAnimation da = new DoubleAnimation(wp.image.Opacity, 0, new Duration(TimeSpan.FromSeconds(0.5)));
                            //wp.image.BeginAnimation(Image.OpacityProperty, da, HandoffBehavior.SnapshotAndReplace);

                            //DoubleAnimation da2 = new DoubleAnimation(wp.path.Opacity, 0, new Duration(TimeSpan.FromSeconds(0.5)));
                            //wp.path.BeginAnimation(Path.OpacityProperty, da2, HandoffBehavior.SnapshotAndReplace);
                            wp.path.Opacity = Math.Max(0, wp.image.Opacity - 0.03);
                            wp.image.Opacity = Math.Max(0, wp.image.Opacity - 0.03);
                            //WPFPreview wp = (WPFPreview)p;
                            //wp.image.Opacity = Math.Min(1, wp.image.Opacity + 0.1);

                            //wp.image.b
                            //wp.image.CaptureMouse
                            //wp.image.Opacity = 0.1;
                        }
                        //wpf.path.Stroke = Brushes.Green;
                    }
                        //Console.
                        //WPFGesture.c
                }
            }

            foreach (int id in removals)
            {
                gestures.Remove(id);
            }

            foreach (List<Preview> l in previews.Values)
            {
                foreach (Preview p in l)
                {
                    p.update();
                }
            }
        }

        private void updatePreviews(int id, string s)
        {
            foreach (Preview p in previews[id])
            {
                WPFPreview wpf = (WPFPreview)p;
                if (wpf.segmentName != s)
                {
                    p.delete();                   
                }
            }
            //throw new NotImplementedException();
        }

        //public void draw()
        //{
            //return;



            // draw preview for every gesture starting point

            //foreach (Gesture g in gestures.Values)
            ///{/
             //   if (!g.isFingerReleased())
              //  {                    
                    //Common.Point startPoint = new Common.Point(g.getPoints()[0]);
                    //startPoint.x *= (1280.0f / 1024.0f);
                    //foreach (List<Preview> l in previews.Values)
                    //{
                        //foreach (Preview p in l)
                        //{
                            //startPoint.x *= (1280.0f / 1024.0f);
                            //p.setTranslation(startPoint.x, startPoint.y);
                            //p.draw();
                        //}
                    //}
              //  }
            //}
        //}



        public void writeGesture(string name)
        {
            XmlWriter xw = XmlWriter.Create(name);
            xw.WriteStartDocument();

            xw.WriteStartElement("gesture");

            // write gesture protocol version
            xw.WriteStartAttribute("version");
            xw.WriteValue(1);
            xw.WriteEndAttribute();

            // write gesture name
            xw.WriteStartAttribute("name");
            xw.WriteValue("parabola");
            xw.WriteEndAttribute();

            // is the gesture uniformly scaled?
            xw.WriteStartAttribute("uniformScaling");
            xw.WriteValue(true);
            xw.WriteEndAttribute();

            

            // the resolution (number of points used to match gesture)
            xw.WriteStartAttribute("resolution");
            xw.WriteValue(64);
            xw.WriteEndAttribute();


            // write point set
            for (int x = -5; x < 6; ++x)
            {
                xw.WriteStartElement("point");
                xw.WriteStartAttribute("x");
                xw.WriteValue(x);
                xw.WriteEndAttribute();
                xw.WriteStartAttribute("y");
                xw.WriteValue(x * x);
                xw.WriteEndAttribute();
                xw.WriteEndElement();
            }

            // write preview texture
            xw.WriteStartElement("preview");
            xw.WriteStartAttribute("source");
            xw.WriteValue("elephant.png");
            xw.WriteEndAttribute();


            xw.WriteStartAttribute("relativeWidth");
            xw.WriteValue(0.02f);
            xw.WriteEndAttribute();
            xw.WriteStartAttribute("relativeHeight");
            xw.WriteValue(0.02f);
            xw.WriteEndAttribute();

            xw.WriteComment("relative-width and relative-height indicate size of preview relative to screen size");

            // TODO: possibly hint _where_ the preview should be drawn. For now I let my framework figure that out.
            xw.WriteComment("TODO: possibly hint _where_ the preview should be drawn. For now I let my framework figure that out.");

            // TODO: add color information for the preview path?

            // TODO: add angle support for segments?

            // TODO: add segment resolution?

            // TODO: write whether is gesture is rotation invariant?
            xw.WriteEndElement();

            // TODO: somehow figure out how to store composed & multi-user gestures
            xw.WriteComment("TODO: somehow figure out how to write / store composed / multi-user gestures");

            // TODO: perhaps add timing constraints? Or distinguish phases in a gesture?
            xw.WriteComment("TODO: perhaps add timing constraints? Or distinguish phases in a gesture?");


            xw.Close();
        }


        // TODO: reader is not very robust.
 //       public void readXML(string name)
        //{


/*            Candidate c = null;

            XmlReader xr = XmlReader.Create(name);
            while (xr.Read())
            {
                switch (xr.NodeType)
                {
                    case XmlNodeType.Element:
                        if (xr.Name == "gesture")
                        {
                            xr.MoveToNextAttribute();
                            if (xr.Name == "version")
                            {
                                if (xr.Value == "1")
                                {
                                    c = readGestureVersion1(xr);
                                }
                            }
                        }
                        break;
                }
            }*/

            //return null;
       // }


        public Common.Point readPoint(XmlReader xr)
        {
            xr.MoveToNextAttribute();
            float x = float.Parse(xr.Value);
            xr.MoveToNextAttribute();
            float y = float.Parse(xr.Value);          
            return new Common.Point(x, y);     
        }


        public void readSegment(XmlReader xr)
        {
            Segment segment = new Segment();
            segment.points = new List<Common.Point>();

            // read attributes
            while (xr.MoveToNextAttribute())
            {
                switch (xr.Name)
                {
                    case "name":
                        segment.name = xr.Value;
                        break;
                    case "uniformScaling":
                         segment.uniformScaling = bool.Parse(xr.Value);
                        break;
                    default:
                        throw new Exception("unknown attribute in readSegment: " + xr.Name);
                }
            }

            // and read set of points
            while (xr.NodeType != XmlNodeType.EndElement) {
                xr.Read();

                if (xr.NodeType == XmlNodeType.Element)
                {
                    if (xr.Name == "point")
                    {
                        segment.points.Add(readPoint(xr));
                    }
                }
            }

            if ((segment.name == null) || (segment.name == ""))
            {
                throw new Exception("no name attribute found in readSegment.");
            }

            segmentsDatabase.Add(segment.name, segment);

        }

        void readPreview(Candidate candidate, XmlReader xr)
        {
            //string source = null;
            //float relativeWidth = 0, relativeHeight = 0;

            while (xr.MoveToNextAttribute())
            {
                switch (xr.Name)
                {
                    case "source":
                        candidate.source = xr.Value;
                        break;
                    case "relativeWidth":
                        candidate.relativeWidth = float.Parse(xr.Value);
                        break;
                    case "relativeHeight":
                        candidate.relativeHeight = float.Parse(xr.Value);
                        break;
                }
            }

            //candidate.re
            

        }

        public void  readGesture(XmlReader xr)
        {
            Candidate candidate = new Candidate();
            //String name;
            List<string> segments = new List<string>();
            List<float> angles = new List<float>();

            // read attributes
            while (xr.MoveToNextAttribute())
            {
                switch (xr.Name)
                {
                    case "name":
                        candidate.name =  xr.Value;
                        break;
                    default:
                        throw new Exception("unknown attribute in readGesture: " + xr.Name);
                }
            }

            // and read all segments
            while (xr.NodeType != XmlNodeType.EndElement)
            {
                xr.Read();

                if (xr.NodeType == XmlNodeType.Element)
                {
                    switch (xr.Name) {
                        case "segment":
                            // TODO: make more robust.
                            xr.MoveToNextAttribute();
                            segments.Add(xr.Value);
                            xr.MoveToNextAttribute();
                            angles.Add(float.Parse(xr.Value));
                            break;
                        case "preview":
                            readPreview(candidate, xr);
                            break;
                        default:
                            throw new Exception("unknown element in readGesture: " + xr.Name);
                    }

                }
            }

            if ((candidate.name == null) || (candidate.name == ""))
            {
                throw new Exception("no name attribute found in readGesture.");
            }

            constructGesture(candidate, segments, angles);
            candidateLibrary.addCandidate(candidate);
        }

        private void constructGesture(Candidate candidate, List<string> segments, List<float> angles)
        {
            //Candidate candidate = new Candidate();
            //candidate.name = name;

            // now construct gesture from all segments;
            candidate.originalPoints = new List<Common.Point>();
            candidate.originalPoints.Add(new Common.Point(0, 0));
            for (int i = 0; i < segments.Count; ++i)
            {
                if (!segmentsDatabase.ContainsKey(segments[i]))
                {
                    throw new Exception("Cannot find segment: " + segments[i] + " for gesture: " + candidate.name);
                }

                tf.appendInPlace(candidate.originalPoints, segmentsDatabase[segments[i]].points, angles[i]);
            }

            candidate.transformedPoints = tf.transform(candidate.originalPoints, candidate.uniformScaling);
            candidate.transformAngle = tf.getTransformAngle();

            //return candidate;
        }



        
        public void ReadXML(string name)
        {
            XmlReader xr = XmlReader.Create(name);
            
            // TODO: add namespace with XML protocol version
            // TODO: possibly hint _where_ the preview should be drawn. For now I let my framework figure that out.
            // TODO: add color information for the preview path?
            // TODO: add angle support for segments?
            // TODO: add segment resolution?
            // TODO: write whether is gesture is rotation invariant?
            // TODO: somehow figure out how to store composed & multi-user gestures
            // TODO: perhaps add timing constraints? Or distinguish phases in a gesture?
            // TODO: add resampling rate somewhere.

            while (xr.Read())
            {
                switch (xr.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (xr.Name)
                        {
                            case "root":
                                break;
                            case "segmentDeclaration":
                                readSegment(xr);
                                break;
                            case "gesture":
                                readGesture(xr);
                                break;
                            default:
                                throw new Exception("unknown element in XML: " + xr.Name);
                        }
                        break;
                    default:
                        break;
                }

            }
        }
    }
}